﻿using System;
using System.Threading;
using System.Threading.Tasks;
using MailSlot;

namespace GurLoader.Service.Mailslot
{
    /// <summary>
    ///     Provides a threaded polling loop around a given mailslot on a windows machine.
    ///     When a message is received durring a polling attempt, an associated event is
    ///     fired as a CommandReceived callback.
    /// </summary>
    public class MailslotService : IDisposable, IMailslotService
    {
        public delegate void CommandReceivedEventHandler(object sender, CommandEventArgs args);

        private readonly MailslotServer mailslotServer;
        private CancellationTokenSource cancellation;
        private Task listenerTask;

        /// <summary>
        ///     Creates a new listener component with the given mailslot name, and a default polling wait time
        ///     of 1000ms ( 1 second ).
        /// </summary>
        /// <param name="mailslotName">Name of the mailslot to poll.</param>
        public MailslotService(string mailslotName) : this(mailslotName, 1000)
        {
        }

        /// <summary>
        ///     Creates a new listener component that will poll the given mailslot at a given
        ///     interval.
        /// </summary>
        /// <remarks>Note that the mailslot will only be polled after calling the Start method.</remarks>
        /// <param name="mailslotName">Name of the mailslot to poll.</param>
        /// <param name="pollingWaitTime">Wait time between poll attempts, in ms. Should be between 1ms and 5 min.</param>
        public MailslotService(string mailslotName, int pollingWaitTime)
        {
            if (string.IsNullOrEmpty(mailslotName))
                throw new ArgumentException("Mailslot name must not be null or empty.");

            if (pollingWaitTime < 1 || pollingWaitTime > (1000*60*5))
                throw new ArgumentException("Polling wait time must be between 1ms and 5 mins.");

            MailslotName = mailslotName;
            PollingWaitTime = pollingWaitTime;

            mailslotServer = new MailslotServer(MailslotName);
        }

        public string MailslotName { get; private set; }
        public int PollingWaitTime { get; private set; }

        public bool IsRunning
        {
            get
            {
                if (listenerTask == null)
                    return false;

                return listenerTask.Status == TaskStatus.Running;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public event CommandReceivedEventHandler CommandReceived;

        /// <summary>
        ///     Begin polling the mailslot.
        /// </summary>
        public void Start()
        {
            cancellation = new CancellationTokenSource();
            CancellationToken token = cancellation.Token;

            listenerTask = Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Thread.Sleep(PollingWaitTime);
                    if (token.IsCancellationRequested)
                        break;

                    string command = mailslotServer.GetNextMessage();
                    if (! string.IsNullOrEmpty(command))
                    {
                        if (CommandReceived != null)
                            CommandReceived(this, new CommandEventArgs(command));
                    }
                }
            }, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
        }

        /// <summary>
        ///     Requests cancellation of the polling loop.
        /// </summary>
        public void Stop()
        {
            if (cancellation != null)
                cancellation.Cancel();

            if (listenerTask != null)
            {
                try
                {
                    listenerTask.Wait(10*1000); // wait for 10 seconds max
                }
                catch (AggregateException aggEx)
                {
                    bool wasCancelled = false;
                    // will throw an aggregate exception with a nested
                    // task cancelled exception if the cancel doesnt complete
                    // before the wait. This is not an error as it
                    // signals successful completion
                    if (aggEx.InnerExceptions.Count == 1)
                    {
                        if (aggEx.InnerException.GetType() == typeof (TaskCanceledException))
                            wasCancelled = true;
                    }

                    if (! wasCancelled)
                        throw aggEx;
                }
            }
        }

        protected virtual void Dispose(Boolean isDisposing)
        {
            if (isDisposing)
            {
                if (listenerTask != null)
                {
                    if (!listenerTask.IsCompleted)
                        Stop();

                    listenerTask.Dispose();
                }

                mailslotServer.Dispose();
            }
        }
    }
}